package hsl.haxe;
import haxe.macro.Expr;

class Binder {
	#if macro
	private static var argumentPackage:Array<String>;
	#end
	@:macro public static function bind(signaler:Expr, listener:Expr):Expr {
		if (null == argumentPackage) {
			argumentPackage = new Array();
			argumentPackage.push("hsl");
			argumentPackage.push("haxe");
			argumentPackage.push("argument");
		}
		var throwError:String -> Position -> Void = haxe.macro.Context.error;
		var bindArguments:Array<Expr> = new Array();
		switch (listener.expr) {
			// In case of a call, such as addColor(value)
			case ECall(callExpression, arguments):
			switch (callExpression.expr) {
				case EConst(constant):
				switch (constant) {
					case CIdent(identifier):
					bindArguments.push(callExpression);
					default:
					throwError(Std.string(callExpression.expr) + " found, where a function was expected", callExpression.pos);
				}
				case EField(expression, field):
				switch (expression.expr) {
					case EConst(constant):
					switch (constant) {
						case CIdent(identifier):
						bindArguments.push(callExpression);
						default:
						throwError(Std.string(expression.expr) + " found, where a function was expected", expression.pos);
					}
					default:
					throwError(Std.string(expression.expr) + " found, where a function was expected", expression.pos);
				}
				default:
				throwError(Std.string(callExpression.expr) + " found, where a function was expected", callExpression.pos);
			}
			for (argument in arguments) {
				bindArguments.push(mapArgument(argument, throwError));
			}
			// In case of an identifier, such as closeScreen
			case EConst(constant):
			switch (constant) {
				case CIdent(identifier):
				bindArguments.push(listener);
				default:
				throwError(Std.string(listener.expr) + " found, where a function was expected", listener.pos);
			}
			// In case of an anonymous function, such as function ():Void {}
			case EFunction(passedFunction):
			bindArguments.push(listener);
			default:
			throwError(Std.string(listener.expr) + " found, where a function was expected", listener.pos);
		}
		return {expr: ECall({expr: EField(signaler, getMethodNameForArgumentCount(bindArguments.length - 1, throwError)), pos: haxe.macro.Context.currentPos()}, bindArguments), pos: haxe.macro.Context.currentPos() };
	}
	#if macro
	private static function createNewConstantArgumentExpression(value:Expr):Expr {
		var arguments:Array<Expr> = new Array();
		arguments.push(value);
		return {expr: ENew({sub: null, name: "ConstantArgument", pack: argumentPackage, params: new Array()}, arguments), pos: haxe.macro.Context.currentPos()};
	}
	private static function createNewCurrentTargetArgumentExpression():Expr {
		return {expr: ENew({sub: null, name: "CurrentTargetArgument", pack: argumentPackage, params: new Array()}, new Array()), pos: haxe.macro.Context.currentPos()};
	}
	private static function createNewDataArgumentExpression():Expr {
		return {expr: ENew({sub: null, name: "DataArgument", pack: argumentPackage, params: new Array()}, new Array()), pos: haxe.macro.Context.currentPos()};
	}
	private static function getMethodNameForArgumentCount(value:Int, throwError:String -> Position -> Void):String {
		return "bind" +
			switch (value) {
				case 0:
				"Niladic";
				case 1:
				"Monadic";
				case 2:
				"Dyadic";
				case 3:
				"Triadic";
				case 4:
				"Tetradic";
				case 5:
				"Pentadic";
				case 6:
				"Hexadic";
				case 7:
				"Heptadic";
				case 8:
				"Octadic";
				default:
				throwError("No more than eight arguments are supported at this time", haxe.macro.Context.currentPos());
				null;
			}
	}
	private static function mapArgument(value:Expr, throwError:String -> Position -> Void):Expr {
		return
			switch (value.expr) {
				case EField(expression, fieldName):
				switch (expression.expr) {
					case EConst(constant):
					switch (constant) {
						case CIdent(name):
						if ("signal" == name) {
							switch (fieldName) {
								case "currentTarget":
								createNewCurrentTargetArgumentExpression();
								case "data":
								createNewDataArgumentExpression();
								default:
								throwError(fieldName + " found, where either \"data\", \"currentTarget\" was expected", haxe.macro.Context.currentPos());
								null;
							}
						} else {
							createNewConstantArgumentExpression(value);
						}
						default:
						createNewConstantArgumentExpression(value);
					}
					default:
					createNewConstantArgumentExpression(value);
				}
				default:
				createNewConstantArgumentExpression(value);
			}
	}
	#end
}
